/*******************************************************************************
 * relationalcloud.com
 *  
 *  Project Info:  http://relationalcloud.com
 *  Project Members:  	Carlo Curino <carlo.curino@gmail.com>
 * 				Evan Jones <ej@evanjones.ca>
 *  				Yang Zhang <yaaang@gmail.com> 
 * 				Sam Madden <madden@csail.mit.edu>
 *  This library is free software; you can redistribute it and/or modify it under the terms
 *  of the GNU General Public License as published by the Free Software Foundation;
 *  either version 3.0 of the License, or (at your option) any later version.
 * 
 *  This library is distributed in the hope that it will be useful, but WITHOUT ANY 
 *  WARRANTY;  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
 *  PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 ******************************************************************************/
package com.relationalcloud.jdbc2;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLNonTransientConnectionException;
import java.sql.SQLWarning;
import java.util.ArrayList;

import com.google.protobuf.ByteString;
import com.relationalcloud.backend.Jdbc.SQLBatchResults;
import com.relationalcloud.backend.Jdbc.SQLResultSet;

public class Statement implements java.sql.Statement {
  private DtxnConnection connection;

  private final ArrayList<String> batchStatements = new ArrayList<String>();

  private ResultSet lastResultSet;
  private int lastUpdateCount;
  private int lastAutoIncrementInsertId;

  public Statement(DtxnConnection connection) {
    this.connection = connection;
    clearLastResults();
  }

  public static boolean isQuery(String sql) {
    return sql.toUpperCase().startsWith("SELECT ");
  }

  public static boolean isUpdate(String sql) {
    sql = sql.toUpperCase();
    return sql.startsWith("INSERT ") || sql.startsWith("UPDATE ") || sql.startsWith("DELETE ");
  }

  public static void checkAutoGeneratedKeys(int autoGeneratedKeys) throws SQLException {
    if (autoGeneratedKeys != Statement.RETURN_GENERATED_KEYS &&
        autoGeneratedKeys != Statement.NO_GENERATED_KEYS) {
      throw new SQLException("invalid value for autoGeneratedKeys = " + autoGeneratedKeys);
    }
  }

  private void clearLastResults() {
    lastResultSet = null;
    lastUpdateCount = -1;
    lastAutoIncrementInsertId = -1;
  }

  public void checkStatementIsOpen() throws SQLNonTransientConnectionException {
    if (connection == null) {
      // This is copied from the MySQL JDBC driver
      throw new SQLNonTransientConnectionException(
          "No operations allowed after statement closed.");
    }
  }

  @Override
  public void addBatch(String statement) throws SQLException {
    checkStatementIsOpen();
    batchStatements.add(statement);
  }

  @Override
  public void cancel() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public void clearBatch() throws SQLException {
    batchStatements.clear();
  }

  @Override
  public void clearWarnings() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public void close() throws SQLException {
    checkStatementIsOpen();
    connection = null;
  }

  @Override
  public boolean execute(String sql) throws SQLException {
    checkStatementIsOpen();
    clearLastResults();
    SQLResultSet r = connection.blockingExecute(sql);
    if (r.hasAffectedRows()) {
      lastUpdateCount = (int) r.getAffectedRows();
      if (r.hasAutoIncrementInsertId()) {
        lastAutoIncrementInsertId = (int) r.getAutoIncrementInsertId();
        assert lastAutoIncrementInsertId > 0;
      }
      return false;
    } else {
      assert !r.hasAutoIncrementInsertId();
      lastResultSet = new ProtoResultSet(r);
      return true;
    }
  }

  @Override
  public boolean execute(String arg0, int arg1) throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public boolean execute(String arg0, int[] arg1) throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public boolean execute(String arg0, String[] arg1) throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public int[] executeBatch() throws SQLException {
    checkStatementIsOpen();
    clearLastResults();

    // Ignore empty batches
    if (batchStatements.size() == 0) {
      if (connection.isStrictEnabled()) {
        throw new IllegalStateException("trying to execute an empty batch");
      }
      return new int[]{};
    }

    SQLBatchResults results = connection.blockingExecute(batchStatements);
    assert results.getResultCount() == batchStatements.size() ||
        results.getResult(results.getResultCount() - 1).hasErrorCode();

    // Throw an exception if a statement failed
    ProtoResultSet.checkError(results.getResult(results.getResultCount() - 1));

    int[] output = new int[results.getResultCount()];
    for (int i = 0; i < output.length; ++i) {
      assert !results.getResult(i).hasErrorCode();
      assert results.getResult(i).getRowCount() == 0;
      assert results.getResult(i).hasAffectedRows();
      output[i] = (int) results.getResult(i).getAffectedRows();
    }
    return output;
  }

  @Override
  public ResultSet executeQuery(String query) throws SQLException {
//    assert isQuery(query);
    boolean isQuery = execute(query);
//    assert isQuery;
    return lastResultSet;
  }

  @Override
  public int executeUpdate(String update) throws SQLException {
    assert isUpdate(update);
    boolean isQuery = execute(update);
//    assert !isQuery;
    return lastUpdateCount;
  }

  @Override
  public int executeUpdate(String update, int autoGeneratedKeys) throws SQLException {
    checkAutoGeneratedKeys(autoGeneratedKeys);
    return executeUpdate(update);
  }

  @Override
  public int executeUpdate(String arg0, int[] arg1) throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public int executeUpdate(String arg0, String[] arg1) throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public Connection getConnection() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public int getFetchDirection() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public int getFetchSize() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public ResultSet getGeneratedKeys() throws SQLException {
    checkStatementIsOpen();
    if (lastAutoIncrementInsertId == -1) {
      if (connection.isStrictEnabled()) {
        throw new IllegalStateException(
            "getGeneratedKeys() called without generated keys in the last results"); 
      }

      return ProtoResultSet.newEmptyResultSet();
    }

    // Generate a fake result set
    assert lastUpdateCount > 0;
    SQLResultSet.Builder results = SQLResultSet.newBuilder();
    for (int i = 0; i < lastUpdateCount; i++) {
      results.addRow(SQLResultSet.Row.newBuilder().addValue(
          ByteString.copyFromUtf8(Integer.toString(lastAutoIncrementInsertId + i))));
    }
    return new ProtoResultSet(results.build());
  }

  @Override
  public int getMaxFieldSize() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public int getMaxRows() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public boolean getMoreResults() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public boolean getMoreResults(int arg0) throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public int getQueryTimeout() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public ResultSet getResultSet() throws SQLException {
    checkStatementIsOpen();
    return lastResultSet;
  }

  @Override
  public int getResultSetConcurrency() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public int getResultSetHoldability() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public int getResultSetType() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public int getUpdateCount() throws SQLException {
    checkStatementIsOpen();
    if (lastUpdateCount == -1 && connection.isStrictEnabled()) {
      throw new IllegalStateException("calling getUpdateCount for a query result");
    }
    return lastUpdateCount;
  }

  @Override
  public SQLWarning getWarnings() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public boolean isClosed() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public boolean isPoolable() throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public void setCursorName(String arg0) throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public void setEscapeProcessing(boolean arg0) throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public void setFetchDirection(int arg0) throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public void setFetchSize(int rows) throws SQLException {
    checkStatementIsOpen();
    if (rows < 0) throw new SQLException("rows must be >= 0 (= " + rows + ")");

    // Ignored in compatibility mode
    // TODO: Actually implement?
    if (connection.isStrictEnabled() && rows > 0)
      throw new UnsupportedOperationException("setFetchSize not implemented");
  }

  @Override
  public void setMaxFieldSize(int arg0) throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public void setMaxRows(int max) throws SQLException {
    checkStatementIsOpen();
    if (max < 0) throw new SQLException("max must be >= 0 (= " + max + ")");

    // Ignored in compatibility mode
    // TODO: Actually implement this.
    if (connection.isStrictEnabled())
      throw new UnsupportedOperationException("setMaxRows not implemented");
  }

  @Override
  public void setPoolable(boolean arg0) throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public void setQueryTimeout(int arg0) throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public boolean isWrapperFor(Class<?> arg0) throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }

  @Override
  public <T> T unwrap(Class<T> arg0) throws SQLException {
    throw new UnsupportedOperationException("TODO: implement");
  }
}
